package com.lancoo.ccas53.aspect;

import cn.hutool.core.util.StrUtil;
import com.lancoo.ccas53.exception.BizException;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RRateLimiter;
import org.redisson.api.RateIntervalUnit;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.expression.MethodBasedEvaluationContext;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.ParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.stereotype.Component;
import org.springframework.util.DigestUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @ClassName RateLimiterAspect
 * @Description 默认全部接口，相同token限流 5次/s. 对于需要特殊限流的接口，使用@RateLimiter注解
 * @Author quin
 * @Date 2023/6/30 11:20
 */
@Slf4j
@Aspect
@Component
public class RateLimiterAspect {

    private ParameterNameDiscoverer discoverer = new DefaultParameterNameDiscoverer();

    private ExpressionParser parser = new SpelExpressionParser();

    @Autowired
    private RedissonClient redissonClient;

    @Pointcut("execution(* com.lancoo.ccas53.controller.*.*(..))")
    public void pc1() {
    }

    @Around("@annotation(rateLimiter)")
    public Object around(ProceedingJoinPoint point, RateLimiter rateLimiter) throws Throwable {
        isAllow(point, rateLimiter);
        return point.proceed();
    }

    @Around(value = " @annotation(rateLimiters)")
    public Object around(ProceedingJoinPoint point, RateLimiters rateLimiters) throws Throwable {
        RateLimiter[] limiters = rateLimiters.value();
        for (RateLimiter rateLimiter : limiters) {
            isAllow(point, rateLimiter);
        }
        return point.proceed();
    }

    private void isAllow(ProceedingJoinPoint point, RateLimiter rateLimiter) {

        // 获取key
        String key = getKey(point, rateLimiter);
        // 此处是为了日志输出
        boolean flag = StrUtil.isNotBlank(key);
        // 类路径+方法，然后计算md5
        String uniqueKey = getUniqueKey((MethodSignature) point.getSignature());
        // key名称
        key = StrUtil.isNotBlank(key) ? uniqueKey + "." + key : uniqueKey;
        // 获取RRateLimiter实例
        RRateLimiter limiter = redissonClient.getRateLimiter(key);
        // 创建令牌桶数据模型，单位时间内产生多少令牌
        limiter.trySetRate(rateLimiter.mode(), rateLimiter.rate(), rateLimiter.rateInterval(), RateIntervalUnit.SECONDS);
        // 尝试访问数据，timeout 时间内，允许获得的数量permits(如果获取失败，返回false)
        // 单位时间内不能获取到1个令牌，则返回，不阻塞
        boolean tryAcquire = limiter.tryAcquire(1, rateLimiter.rateInterval(), TimeUnit.SECONDS);
        log.info("【{}】请求，线程：【{}】，获取令牌的结果：{}", flag ? "单用户" : "多用户", Thread.currentThread().getName(), tryAcquire);
        if (!tryAcquire) {
            log.error("限流模式：{}; 限流数量：{}; 限流时间间隔：{}",
                    rateLimiter.mode().toString(), rateLimiter.rate(), rateLimiter.rateInterval());
            throw new BizException(rateLimiter.showPromptMsg());
        }
    }

    private String getUniqueKey(MethodSignature signature) {
        String format = String.format("%s.%s", signature.getDeclaringTypeName(), signature.getMethod().getName());
        return DigestUtils.md5DigestAsHex(format.getBytes(StandardCharsets.UTF_8));
    }

    public String getKey(JoinPoint joinPoint, RateLimiter rateLimiter) {
        List<String> keyList = new ArrayList<>();
        Method method = getMethod(joinPoint);
        List<String> definitionKeys = getSpelDefinitionKey(rateLimiter.keys(), method, joinPoint.getArgs());
        keyList.addAll(definitionKeys);
        return StringUtils.collectionToDelimitedString(keyList, ".", "", "");
    }

    private Method getMethod(JoinPoint joinPoint) {
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();
        if (method.getDeclaringClass().isInterface()) {
            try {
                method = joinPoint.getTarget().getClass().getDeclaredMethod(signature.getName(),
                        method.getParameterTypes());
            } catch (Exception e) {
                log.error(null, e);
            }
        }
        return method;
    }

    private List<String> getSpelDefinitionKey(String[] definitionKeys, Method method, Object[] parameterValues) {
        List<String> definitionKeyList = new ArrayList<>();
        for (String definitionKey : definitionKeys) {
            if (definitionKey != null && !definitionKey.isEmpty()) {
                EvaluationContext context = new MethodBasedEvaluationContext(null, method, parameterValues, discoverer);
                String key = parser.parseExpression(definitionKey).getValue(context).toString();
                definitionKeyList.add(key);
            }
        }
        return definitionKeyList;
    }

}
